FUNCTION TreeMenu with deferred children, resultBlock and selectBlock
ST-VERSIONS 4.1
PREREQUISITES
CONFLICTS
DISTRIBUTION world
VERSION 2.1
DATE October 1992
SUMMARY Class TreeMenu divorces child menus from the menu's values, thus allowing greater control over menu construction. The programmer may specify a collection of values and also a collection of children. Each child may itself be a menu (<PopUpMenu|TreeMenu>) or a one-argument block which will evaluate to a menu.
A 'resultBlock' may also be specified which may be used to combine the values of each of the selected child menus. Also, if the menu is provided with a string for a heading, the menu will startup with that heading. TreeMenu returns #noSelection when no selection is made, rather than 0, this means that menus can be used for numbers.
A 'selectBlock' may be specified which is evaluated each time the menu has
a selection.
An addtional support class is provided -- TreeMenuTracker, and other changes have been made to existing menu classes (PopUpMenu and MenuTracker) to support the functionality of TreeMenu.
See TreeMenu class examples. BH, 10/3/93"!
'From Objectworks\Smalltalk(R), Release 4.1 of 15 April 1992 on 10 March 1993 at 2:59:57 am'!
MenuTracker comment:
'Class MenuTracker opens a window to display a PopUpMenu and tracks
the user''s menu selection.
Since normally only one menu is displayed at a time, we cache one
window to display the current menu in (to make menu pop-up go faster).
Instance variables:
menu <PopUpMenu> the PopupMenu to track
frame <Rectangle> the spatial display box of the menu
insideFrame <Rectangle> the inner display box of the menu
marker <Rectangle> the selection marker
origin <Point> a translation for scrolling
sensor <WindowSensor> the window''s sensor
gc <GraphicsContext> a graphics context to display on
para <TextLines> a display object representing the menu
currentMenu <PopUpMenu | nil> the current submenu
ignoreInitialDamage <Boolean> whether to ignore the initial damage event
Class variables:
CachedWindow <Window | nil> cached pop-up window used to hold menu
Amendments made by Bernard Horan, 5 October 1992:
Changes to three methods that refer to childre to support TreeMenu.
Amendments made by Bernard Horan, 10/3/93:
Changes to a number of methods that refer to the instance variable para; modified to send the message self para'!
'Class PopUpMenu represents a list of items. Its instances are presented on the display screen in a rectangular area. The user points to an item, pressing a mouse button; the item is highlighted. When the button is released, the highlighted item indicates the selection.
The startUp* messages return 0 if no item was selected, otherwise they return the relevant object from the values collection. If no values collection is specified, values is initialized to an Interval, so that the index itself is returned.
If the selected item in the values collection is a PopUpMenu, then it is treated specially: rather than being returned, the selected menu starts up. The user can therefore create a hierarchical menu merely by placing menus in the values collection.
Instance Variables:
labels <Array of: CharacterArray> of menu items
lineArray <Array> of integers indicating where lines should be drawn in the menu
selection <Integer> index into menu items; if 0, no selection
lastSelection <Integer> index of the last menu selection
values <SequenceableCollection> Collection of objects to return when selected.
hasSubMenus <Boolean | nil> the menu has sub menus?
Class Variables:
MenuStyle <TextAttributes> the default TextAttributes to use for menus
SubMenuImage <Image> the default image to show a sub menu
Amendments made by Bernard Horan, 5 October 1992:
New instance protocol called ''children'' containing methods required by TreeMenu.'!
!PopUpMenu methodsFor: 'testing'!
hasSubMenuAt: anIndex
"Answer the receiver has sub menus or not."
"Amended to support TreeMenus.
Bernard Horan, 5 October 1992"
^(self children at: anIndex) isKindOf: PopUpMenu! !
'Class TreeMenu extends PopUpMenu by having a specific pointer to its child menus.
The startUp* messages (inherited from PopUpMenu) return #noSelection if no item was selected, otherwise they return the relevant object from the values collection. If no values collection is specified, values is initialized to an Interval, so that the index itself is returned.
The children collection may contain menus (see example1) or one-argument blocks
which when evaluated with a menu return a menu (see example2).
The user may also specify a two-argument resultBlock into which the parent and child
values are substituted (see example1/example2).
The user may also specify a two-argument selectBlock into which the menu and its tracker are substituted. The selectBlock is evaluated whenever there is a selection (so the expression <menu valueAtSelection> is guaranteed to return a value).
If the menu has a heading then it starts up with it.
Instance variables
children <SequenceableCollection (menu|BlockClosure)|nil>
resultBlock <nil|BlockClosure>
heading <nil|String>
selectBlock <nil | BlockClosure>
Bernard Horan, 5 October 1992'!
!TreeMenu methodsFor: 'testing'!
hasSubMenuAt: anIndex
"Answer the receiver has sub menus or not."
^(super hasSubMenuAt: anIndex)
or:
[| child |
child := children at: anIndex.
(child isKindOf: BlockClosure)
and: [(child value: self)
isKindOf: PopUpMenu]]!
hasSubMenus
"Answer the receiver has sub menus or not."
hasSubMenus isNil
ifTrue:
[hasSubMenus := false.
1 to: children size do: [:index | (self hasSubMenuAt: index)
ifTrue:
[hasSubMenus := true.
^true]]].
^hasSubMenus! !
!TreeMenu methodsFor: 'accessing'!
accept
" The user forced an 'accept'
without selecting any entry. "
selection := #noSelection!
childAt: anIndex
^children at: anIndex!
childAt: anIndex put: aChild
^children at: anIndex put: aChild!
children
^children!
children: aSequenceableCollection
children := aSequenceableCollection!
heading
^heading!
heading: aString
heading := aString!
labelAt: anIndex put: aText
labels at: anIndex put: aText.!
resultBlock
^resultBlock!
resultBlock: aBlock
resultBlock := aBlock!
selectBlock: aBlock
"aBlock should be a one argument block. The argument will be me"
selectBlock := aBlock! !
!TreeMenu methodsFor: 'private'!
childAtSelection
"Answer the item current selected."
(selection == #noSelection or: [selection > children size])